/* eslint-disable max-len */
/**
 * CRUD 视图模型
 * 提供基于RESTapi方式的CRUD操作及数据托管
 * @author holyhigh
 */
import {
  isArray, map, assign, get, isUndefined, each, isObject, partial, isNull,
} from '@holyhigh/func.js';
import {toObject} from '@holyhigh/func.js/object';

// eslint-disable-next-line require-jsdoc
function viewSetter(v, prop, bind) {
  bind['_'+prop] = v;
}
// eslint-disable-next-line require-jsdoc
function viewGetter(prop, bind) {
  const v = bind['_'+prop];
  if (!isNull(v)) return v;
  return isUndefined(CRUD.defaults.view[prop])?true:CRUD.defaults.view[prop];
}

const VIEW_PROPS = [
  'queryShow', 'queryReset', 'opAdd',
  'opEdit', 'opDel', 'opExport',
];

/**
 * 每个服务对应一个CRUD实例
 * 提供查询、表格、分页等逻辑托管
 * 通过提供state和action实现UI分离并支持逻辑重写
 *
 * @param {object | string} restURL 如果是对象类型，url属性为restURL，其他属性可以通过params获取
 */
function CRUD(restURL) {
  let url = restURL;
  this.params = {};
  if (isObject(restURL)) {
    url = restURL.url;
    this.params = Object.freeze(restURL);
  }
  this.__restURL = url;
  this.__urlParams = {};
  const state = {
    view: { // 业务组件通过view来控制UI
    },
    loading: { // 通过loading状态控制加载
      query: false, // 搜索中
      table: false, // 加载中
      del: false,
      export: false, // 导出中
      form: false, // 表单数据加载中，包括编辑、查看
      submit: false, // 表单提交中
    },
    query: {}, // 用于searchBar
    table: {
      data: [// 表单数据托管
      ],
      selection: [], // 当前选中行
      allColumns: [], // 表格所有列，用于动态展示
      orders: [], // 排序列表
    },
    pagination: { // 分页数据托管
      _pageSize: null,
      set pageSize(v) {
        this._pageSize = v;
      },
      // 1. 如果CRUD实例设置了pageSize，以实例为准
      // 2. 如果实例值不合法取defaults
      get pageSize() {
        const ps = parseInt(this._pageSize);
        if (ps) return ps;
        return CRUD.defaults.pagination.pageSize || 15;
      },
      currentPage: 1,
      total: 100,
    },
    formStatus: 0, // 1：新增；2：编辑；3：查看。适用于组合弹窗或细分弹窗
    form: {
    },
    error: {
      name: '',
      message: '',
      status: '',
    },
  };

  const viewProps = {};
  each(VIEW_PROPS, (prop)=>{
    viewProps[prop] = {
      set: partial(viewSetter, undefined, prop, state.view),
      get: partial(viewGetter, prop, state.view),
    };
    state.view['_'+prop] = null;
  });
  Object.defineProperties(state.view, viewProps);

  assign(this, state);
}

// methods
assign(CRUD.prototype, {
  setURLParams(v) {
    this.__urlParams = v;
  },
  getRestURL() {
    const params = toObject(this.__urlParams);
    return this.__restURL.replace(/:([^:]+?)(\/|$)/mg, (a, b, c)=>params[b]+c);
  },
  async toQuery() {
    this.loading.query = this.loading.table = true;

    let params = {
      ...this.query,
      ...this.pagination,
    };

    callHook(CRUD.HOOK.BEFORE_QUERY, this, params, this.table.orders);
    params = toQueryString(params);

    try {
      const rs = await this.doQuery(params);

      this.loading.query = this.loading.table = false;

      callHook(CRUD.HOOK.AFTER_QUERY, this, rs);
    } catch (error) {
      this.loading.query = this.loading.table = false;
      callHook(CRUD.HOOK.ON_ERROR, this, error);
    }
  },
  async toDelete(rows) {
    rows = isArray(rows) ? rows : [rows];
    this.loading.del = this.loading.table = true;
    let data = rows;

    // 通过rowKey自动转为id数组
    if (this.table.rowKey) {
      data = map(rows, (item) => get(item, this.table.rowKey));
    } else {
      crudWarn(`table.rowKey is a blank value [${this.table.rowKey}], it may cause an error`, '- toDelete()');
    }

    callHook(CRUD.HOOK.BEFORE_DELETE, this, data);

    try {
      const rs = await this.doDelete(data);

      this.loading.del = this.loading.table = false;

      callHook(CRUD.HOOK.AFTER_DELETE, this, rs);
      // 刷新当前页
      this.toQuery();
    } catch (error) {
      this.loading.del = this.loading.table = false;
      callHook(CRUD.HOOK.ON_ERROR, this, error);
    }
  },
  async toExport() {
    this.loading.export = true;

    let params = {
      ...this.query,
      ...this.pagination,
    };

    callHook(CRUD.HOOK.BEFORE_EXPORT, this, params, this.table.orders);
    params = toQueryString(params);

    try {
      const rs = await this.doExport(params);

      this.loading.export = false;
      callHook(CRUD.HOOK.AFTER_EXPORT, this, rs);
    } catch (error) {
      this.loading.export = false;
      callHook(CRUD.HOOK.ON_ERROR, this, error);
    }
  },
  /**
   * 打开新增form
   * 设置formStatus为1
   * @param  {...any} args 参数，可以传递给BEFORE_ADD钩子。在实现基于某个父节点下新增的场景很有用
   */
  toAdd(...args) {
    callHook(CRUD.HOOK.BEFORE_ADD, this, ...args);
    this.formStatus = 1;
  },
  toEdit(row) {
    this.formStatus = 2;

    let id = '-';
    if (this.table.rowKey) {
      id = row[this.table.rowKey];
    } else {
      crudWarn(`table.rowKey is a blank value [${this.table.rowKey}], it may cause an error`, '- toEdit()');
    }
    this.loading.form = true;

    CRUD.request(
        {url: this.getRestURL() + CRUD.RESTAPI.QUERY + '/' + id,
          method: 'GET',
        },
    ).then((rs) => {
      callHook(CRUD.HOOK.BEFORE_EDIT, this, rs);
      this.loading.form = false;
    }).catch((error) => {
      this.loading.form = false;
      callHook(CRUD.HOOK.ON_ERROR, this, error);
    });
  },
  toView(row) {
    this.formStatus = 3;

    let id = '-';
    if (this.table.rowKey) {
      id = row[this.table.rowKey];
    } else {
      crudWarn(`table.rowKey is a blank value [${this.table.rowKey}], it may cause an error`, '- toView()');
    }
    this.loading.form = true;

    CRUD.request(
        {url: this.getRestURL() + CRUD.RESTAPI.QUERY + '/' + id,
          method: 'GET',
        },
    ).then((rs) => {
      callHook(CRUD.HOOK.BEFORE_VIEW, this, rs);
      this.loading.form = false;
    }).catch((error) => {
      this.loading.form = false;
      callHook(CRUD.HOOK.ON_ERROR, this, error);
    });
  },
  // 取消表单
  cancel() {
    this.formStatus = 0;
    callHook(CRUD.HOOK.ON_CANCEL, this);
  },
  // 提交表单
  async submit(formName) {
    let valid = false;
    try {
      valid = await this.doValidate(formName);
    } catch (error) {
      crudError('form validation', error);
    }

    if (valid) {
      callHook(CRUD.HOOK.BEFORE_SUBMIT, this);

      this.loading.submit = true;
      try {
        let rs;
        if (this.formStatus === 1) {
          rs = await this.doAdd();
        } else if (this.formStatus === 2) {
          rs = await this.doUpdate();
        }

        callHook(CRUD.HOOK.AFTER_SUBMIT, this, rs);
      } catch (error) {
        callHook(CRUD.HOOK.ON_ERROR, this, error);
      }

      this.loading.submit = false;
    }
  },
  // 刷新页面，适用于查询条件变更后需要重新加载的场景
  reload() {
    this.pagination.currentPage = 1;
    this.toQuery();
  },
});
// actions
assign(CRUD.prototype, {
  // 执行查询操作
  doQuery(params) {
    return CRUD.request(
        {url: this.getRestURL() + CRUD.RESTAPI.QUERY + '?' + params,
          method: 'GET',
        },
    );
  },
  // 执行新增操作
  doAdd() {
    return CRUD.request(
        {url: this.getRestURL() + CRUD.RESTAPI.ADD,
          method: 'POST',
          data: this.form,
        },
    );
  },
  // 执行编辑操作
  doUpdate() {
    return CRUD.request(
        {url: this.getRestURL() + CRUD.RESTAPI.UPDATE,
          method: 'PUT',
          data: this.form,
        },
    );
  },
  // 执行删除操作
  doDelete(data) {
    return CRUD.request(
        {url: this.getRestURL() + CRUD.RESTAPI.DELETE,
          method: 'DELETE',
          data: data,
        },
    );
  },
  // 执行导出操作
  doExport(params) {
    return CRUD.request(
        {url: this.getRestURL() + CRUD.RESTAPI.EXPORT + '?' + params,
          method: 'GET',
        },
    );
  },
  // 成功返回true
  // 适配器实现
  doValidate() {
    return true;
  },
});

// eslint-disable-next-line require-jsdoc
function toQueryString(param) {
  const str = [];
  each(param, (v, k)=>{
    if (isArray(v)) {
      each(v, (item, i)=>{
        str.push(
            encodeURIComponent(k+`[${i}]`) +
          '=' +
          encodeURIComponent(item),
        );
      });
    } else if (isObject(v)) {
      each(v, (itemV, itemK)=>{
        str.push(
            encodeURIComponent(k+`[${itemK}]`) +
          '=' +
          encodeURIComponent(itemV),
        );
      });
    } else {
      str.push(encodeURIComponent(k) + '=' + encodeURIComponent(v));
    }
  });
  return str.join('&');
}

// 全局defaults
CRUD = bindDefaults(CRUD);

/**
 * 调用hook
 * 如果指定了defaults 的hook会叠加调用
 * @param {*} hookName 钩子名称
 * @param {*} crud crud实例
 */
function callHook(hookName, crud, ...args) {
  if (CRUD.defaults[hookName])CRUD.defaults[hookName](crud, ...args);
  if (crud.vm[hookName])crud.vm[hookName](crud, ...args);
  if (hookName === CRUD.HOOK.ON_ERROR) {
    const e = args[0]||{};
    const error = {
      name: e.code || e.name,
    };
    crud.error.name = e.code || e.name;
    if (e.response) {
      error.message = e.response.statusText;
      error.status = e.response.status;
    } else {
      error.message = e.message;
    }
    crud.error = error;
  }
}

/**
 * 内部使用的默认属性
 * @param {*} o
 * @return {object}
 */
export function bindDefaults(o) {
  o.defaults = {
    query: {}, // 查询对象默认值
    view: {},
    pagination: {},
  };
  return o;
}

// 服务钩子，可以定义全局、实例钩子并支持顺序调用
CRUD.HOOK = {
  BEFORE_QUERY: 'CRUD_BEFORE_QUERY', // 查询前回调，可以修改请求参数，比如分页名称等
  AFTER_QUERY: 'CRUD_AFTER_QUERY', // 查询后回调，可以获取查询结果
  BEFORE_DELETE: 'CRUD_BEFORE_DELETE', // 删除前调用
  AFTER_DELETE: 'CRUD_AFTER_DELETE', // 删除后调用
  BEFORE_ADD: 'CRUD_BEFORE_ADD', // 新增前调用，可以用来清空表单或产生uuid等
  BEFORE_EDIT: 'CRUD_BEFORE_EDIT', // 编辑前调用，可以用来锁定某些字段等
  BEFORE_SUBMIT: 'CRUD_BEFORE_SUBMIT', // 提交前调用，可以用来处理form字段
  AFTER_SUBMIT: 'CRUD_AFTER_SUBMIT', // 提交后调用，可以用来刷新页面、发送通知或者其他操作
  BEFORE_EXPORT: 'CRUD_BEFORE_EXPORT',
  AFTER_EXPORT: 'CRUD_AFTER_EXPORT', // 获取导出数据后调用
  ON_ERROR: 'CRUD_ON_ERROR', // 操作发生错误时调用，包括CRUD
  ON_CANCEL: 'CRUD_ON_CANCEL', // 表单编辑取消时触发
};
// REST APIs
CRUD.RESTAPI = {
  QUERY: '',
  EXPORT: '/export',
  ADD: '',
  UPDATE: '',
  DELETE: '',
};

// eslint-disable-next-line require-jsdoc
export function crudWarn(...args) {
  console.warn('[CRUD] - ', ...args);
}

// eslint-disable-next-line require-jsdoc
export function crudError(...args) {
  console.error('[CRUD] - ', ...args);
}

/**
 * 查找CRUD实例
 * 用于自定义组件中
 * @param {*} comp
 * @return {CRUD}
 */
export function lookupCRUD(comp) {
  // 向上查找最近的$crud
  let p = comp;
  while (p && p !== comp.$root) {
    if (p.$crud) {
      return p.$crud;
    } else if (p.$cruds) {
      const tag = comp.$attrs.crud || comp.$options.propsData.crud;
      if (!tag) {
        crudError(`Multiple instances detected, but no crud tag found on ${comp.$vnode.componentOptions.tag}`);
        return null;
      }
      return p.$cruds[tag];
    }
    p = p.$parent;
  }
}

// crud组件使用的mixins
CRUD.mixins = {
  data() {
    return {
      $crud: {},
    };
  },
  beforeCreate() {
    this.$crud = lookupCRUD(this);
    if (isUndefined(this.$crud)) {
      // 找不到crud业务组件，报错
      crudError(`Cannot find $crud or $cruds on ${this.$vnode.componentOptions.tag}`);
      return;
    }
  },
};

export default CRUD;
